<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>JS 中的闭包 和 用途</title>
    <script>

       // 从中我们发现 其实闭包并不是 JS 独有的 在其他语言中也有   
       // 参考链接 :  https://www.cnblogs.com/yunfeifei/p/4019504.html
       /*
       Javascript允许使用内部函数---即函数定义和函数表达式位于另一个函数的函数体内。
       而且，这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。
       当其中一个这样的内部函数在包含它们的外部函数之外被调用时，就会形成闭包。
       */

       // 下面是作为一中对比的简单的写法 
        function Cirlce(r){
            this.r = r;
        }

        Cirlce.PI = 3.1415926;
        Cirlce.prototype.area = function (){
            return Cirlce.PI * this.r * this.r;
        }

        function testCircle(){
            var c = new Cirlce(1.0)
            alert(c.area())
        }

        /*
        第二种写法 计算圆的面积
        */
        var cirCleTwo = function(){

            var obj = new Object()
            obj.PI = 3.1415926;
            obj.area = function (r){
                return this.PI * r * r;
            }
            return obj;
        }

        function testCirlceTwo(){
            var c = new cirCleTwo()
            alert(c.area(1))
        }

        /*
        第三种写法  计算面积 
        */

        var circleThree = new Object()
        circleThree.PI = 3.14159;
        circleThree.Area = function (r){
            return this.PI * r * r
        }

        function testCircleThree(){
            alert(circleThree.Area(1.0));
        }
        
        /*
        第四种写法 写法不同 
        */
        function testCircleFour(){
            var  circle = {
                "PI":3.1415926,
                "area":function(r){
                    return this.PI * r * r
                }
            };
            alert(circle.area(1.0))
        }

        /*
        第五种写法
        */
        function testCircleFive(){
            
            //第5种写法  
            var Circle = new Function("this.PI = 3.14159;this.area = function( r ) {return r*r*this.PI;}");  
            
            alert( (new Circle()).area(1.0) );  
        }

        /*
        JS  中常用的 Prototype 
        */
        function testPrototype(){
            var dom = function(){

            };

            dom.Show = function(){
                alert("Show Message");
            };
            
            dom.prototype.Display = function(){
                alert("Property Message");
            };

            // dom.Display(); // Uncaught TypeError: dom.Display is not a function
            dom.Show();  // 正确 

            var d = new dom()
            d.Display()  // 这个可以调用  
            d.Show(); // Uncaught TypeError: d.Show is not a function

            // 得出一个结论 
            /*
            1、不使用prototype属性定义的对象方法，是静态方法，只能直接用类名进行调用！另外，此静态方法中无法使用this变量来调用对象其他的属性！
　　         2、使用prototype属性定义的对象方法，是非静态方法，只有在实例化后才能使用！其方法内部可以this来引用对象自身中的其他属性！
            */
        }

        /*
        访问函数的内部变量 这个有问题  需要单独的 Js 的变量作用域 来写 
        函数中变量的作用域没搞明白
        */ 
        function testFuncInternalVar(){
            
            var dom = function(){
            var Name = "Default";
            this.Sex = "Boy";
            this.success = function(){
                alert("Success");
               };

            };

            alert(dom.Name);  // undefined 
            alert(dom.Sex); // undefined

            // 作为对比的函数  
            var html = {
                Name:'Object',
                Success:function(){
                    this.Say = function(){
                            alert("Hello,world");
                    };
                    alert("Obj Success");
                }
            }; 
            alert(html.Name);  // Object
            html.Success();  // Obj Success

            // 现在 如何 访问 Success 中的 Say 方法  ？ 
            // 由于作用域的限制，是访问不到的。所以要用下面的方法访问
            var  s = new  html.Success();
            s.Say()  

            // 或者 新增的方法写到外面
            html.Success().prototype.Show = function(){
                alert("HaHa")
            }

            var s = new html.Success();
            s.Show()

        }

        
        /*
        JavaScript 闭包的用途
        */
        /*
        我们知道所有的变量，如果不加上var关键字，则默认的会添加到全局对象的属性上去，这样的临时变量加入全局对象有很多坏处，
        比如：别的函数可能误用这些变量；造成全局对象过于庞大，影响访问速度(因为变量的取值是需要从原型链上遍历的)。
        除了每次使用变量都是用var关键字外，我们在实际情况下经常遇到这样一种情况，即有的函数只需要执行一次，其内部变量无需维护，
        比如UI的初始化，那么我们可以使用闭包
        */
        // 测试结果缓存
        var CacheSearchBox = (function (){
            var cache = {}   // 这个
            // var cache = new Array()     // 这个也可以使用
        
            return {
                attachSearchBox: function(dsid){
                    if (dsid in cache){
                        return cache[dsid];
                    }
                    var fsb = Math.ceil(Math.random()*10) + "simultate cache"
                    cache[dsid] = fsb;  // 更新缓存
                    return fsb;
                },
                clearSearchBox:function (dsid){
                    for(var key in cache){
                        delete cache[key];  // 删除掉所有的 元素 
                    }
                },
                showSearchBoxs:function(){
                    for (var key in cache){   // 设置同一个key 值 会产生覆盖
                        console.log('key is:' + key + '，value is:' + cache[key]);
                    }
                }

            }
        })();

        function testResultCache(){
            CacheSearchBox.attachSearchBox('input')
            CacheSearchBox.attachSearchBox('ok')
            CacheSearchBox.attachSearchBox('let')
            CacheSearchBox.attachSearchBox('go')
            CacheSearchBox.showSearchBoxs()
        }

        /*
        Js 闭包的第二个用途
        使用 封装性 实现 其他语言中的 私有变量这个概念 Js默认所有对象的都是公共的变量
        */
        var person = function(){
            // 变量作用域 为函数内部，外部无法访问  因此在函数执行完后会立刻释放资源，关键是不污染全局对象。
            var name = "default";
            return {
                getName: function(){
                    return name;
                },
                setName: function(newName){
                    name = newName;
                }
            }
        }

        function testPrivatePerson(){
            print(person.name);//直接访问，结果为undefined    
            print(person.getName());  //  default     
            person.setName("abruzzi");   
            print(person.getName()); // abruzzi
        }

        /*
        使用闭包 和 prototype 实现类和继承 
        */
        var  p = new person()
        p.setName('Tom')
        
        var Jack = function (){

        };
        Jack.prototype = new person()

        // 函数的prototype 添加私有方法
        Jack.prototype.Say = function (){
            alert('Hello ,my name is Jack')
        }

        /*
        测试 JS 继承的机制 
        */
        function testJackInherit(){
            var j = new Jack()
            j.setName("Jack")  // setName 方法是父类的方法 
            j.Say()
            alert("j's name is :" + j.getName())
        }

    </script>
</head>
<body>
    <button onclick="testCircle()">第一种写法 测试圆形</button>
    <button onclick="testCirlceTwo()">第二种写法 测试圆形</button>
    <button onclick="testCircleThree()">第三种 测试圆形</button>
    <button onclick="testCircleFour()">第四种 测试圆形</button>
    <button onclick="testCircleFive()">第五种 测试圆形</button>
    <button onclick="testPrototype()">我们测试 prototype</button>
    <button onclick="testFuncInternalVar()">我们测试 调用函数内部的变量 (变量的作用域)</button>
    <button onclick="testResultCache()">测试闭包的简单使用</button>
    <button onclick="testPrivatePerson()">测试私有对象的 简单使用</button>
    <button onclick="testJackInherit()">测试 继承</button>
</body>
</body>
</html>